-
Notifications
You must be signed in to change notification settings - Fork 86
Actions Board View Rule #2249
base: master
Are you sure you want to change the base?
Actions Board View Rule #2249
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2249 +/- ##
==========================================
+ Coverage 69.52% 71.25% +1.73%
==========================================
Files 176 177 +1
Lines 16670 18079 +1409
==========================================
+ Hits 11589 12882 +1293
- Misses 3957 3994 +37
- Partials 1124 1203 +79
Continue to review full report at Codecov.
|
actions/actions.go
Outdated
@@ -72,14 +72,12 @@ func ExecuteActionsByChangeset(ctx context.Context, db application.DB, userID uu | |||
Ctx: ctx, | |||
UserID: &userID, | |||
}, actionConfig, newContext, contextChanges, &actionChanges) | |||
/* commented out for now until this rule is added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please line comments. Then it is easier to follow what is affected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok.
) | ||
|
||
// ActionStateToMetaState implements the bidirectional mapping between state and column. | ||
type ActionStateToMetaState struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a little bit strange to have an action defined inside the rules
package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because the rule is the action. actions/actions
would be weird, so does action/impls
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelkleinhenz what do you mean with because the rule is the action?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only reason there is a rules
directory is to structure the files and to seperate the infra from the actual actions that may go away at some point as the actions will be stored somewhere else, like in the database. That rules
directory is just to keep the files clean and structures. The rules are not rules but the semantic name is action
.
@@ -3,7 +3,7 @@ Package actions system is a key component for process automation in WIT. It prov | |||
way of executing user-configurable, dynamic process steps depending on user | |||
settings, schema settings and events in the WIT. | |||
|
|||
The idea here is to provide a simple, yet powerful "signal-slot" system that | |||
The idea here is to provide a simple, yet powerful "publish-subscribe" system that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have almost the same comment without relating to a package or something in actions/actions.go
. Please remove the latter one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelkleinhenz great. But please try to use one commit per comment so it is easy to follow as a reviewer.
// make sure the rule is implementing the interface. | ||
var _ Action = ActionStateToMetaState{} | ||
|
||
func (act ActionStateToMetaState) containsUUID(s []uuid.UUID, e uuid.UUID) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of implementing this function here, I encourage you to use this:
Line 110 in b0ee723
func (s Slice) Contains(v uuid.UUID) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get a []uuid.UUID
from WorkItemTypeGroup.type
. So I'd need to convert that into a id.Slice with an extra step, requiring more memory. So I'd suggest we update the model first to not use []uuid.UUID
at all before this. I'd also say id.Slice
should have something like fromSlice()
or similar to make this more convenient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, you can just assign an []uuid.UUID
to an id.Slice
. See this example: https://play.golang.org/p/aIsXiZJZisU
package main
import (
"fmt"
)
type MySlice []int
func main() {
foo := []int{1,2,3}
var mySlice MySlice = foo
fmt.Println(mySlice)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. 4764322
} | ||
// removed in slice2 | ||
for _, elem := range old { | ||
if !act.contains(new, elem) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This causes another unnecessary l loop. For id.Slice
we also have implemented a Diff method that you might want to copy the algorithm from and adapt it slightly: https://github.com/fabric8-services/fabric8-wit/blob/master/id/slice.go#L14 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need added and removed items seperately. So I don't think this improves on the complexity. Also, using id.Slice
needs more memory here, for converting the Slices to id.Slice
first and for the counter map.
if !ok { | ||
return nil, errs.New("metastate value in value list is not of type string") | ||
} | ||
// this is important: we only add a new entry if there |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is very hard to follow. Please try to simplify it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
return nil, errs.New("given field on workitemtype " + wit.ID.String() + " is not an enum field: " + fieldName) | ||
} | ||
|
||
func (act ActionStateToMetaState) getStateToMetastateMap(workitemTypeID uuid.UUID) (map[string]string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please provide a description with this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
return stateToMetastateMap, nil | ||
} | ||
|
||
func (act ActionStateToMetaState) getMetastateToStateMap(workitemTypeID uuid.UUID) (map[string]string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please provide a description with this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
return &c1 | ||
} | ||
|
||
// OnChange executes the action rule. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please have a comment // OnChange implements action.Action
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. It is not fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get the impression we're on different branches. This was fixed. I updated the comment to include the implements reference.
for _, board := range boards { | ||
for _, column := range board.Columns { | ||
if columnID == column.ID.String() { | ||
thisColumn = column |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we break after this find or do we have to continue looping?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking out of nested loops is not that easy and according to https://stackoverflow.com/questions/51996175/how-to-break-out-of-nested-loops-in-golang all solutions proposed there would make the code more complicated and hard to understand. Given that we're only dealing with a small number of columns here (I'd say in 99% of the cases, less than 5), I would say let's prevail code readability in favour of having the WIT count from 3 to 5.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. I'm fine with this.
if err != nil { | ||
return nil, nil, err | ||
return nil, nil, errs.Wrap(err, "error loading space: "+err.Error()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to stringify the error here. Please get rid of err.Error()
. You've already wrapped this error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in feecf1c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix is fine. But maybe it makes sense to specify which space could not be loaded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is contained in the wrapped error. It contains the ID that is not found in the message. I don't see a reason for repeating this.
} | ||
groups, err := act.Db.WorkItemTypeGroups().List(act.Ctx, space.SpaceTemplateID) | ||
if err != nil { | ||
return nil, nil, errs.Wrap(err, "error loading type groups: "+err.Error()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, please get rid of err.Error()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in feecf1c
@michaelkleinhenz can you please explain in more details what this does and what it is good for? This description is only telling what is contained but not what it does. |
return false | ||
} | ||
|
||
func (act ActionStateToMetaState) removeElement(s []interface{}, e interface{}) []interface{} { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot find a test for this function. As simple as it looks this is really dangerous. First, it can be wrong. Second, I think you're leaking memory here. See this for an explanation:
NOTE If the type of the element is a pointer or a struct with pointer fields, which need to be garbage collected, the above implementations of Cut and Delete have a potential memory leak problem: some elements with values are still referenced by slice a and thus can not be collected.
(source https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 35e6034
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've commented on the revision 35e6034. The issue is different now. The functions removeElement
and contains
should not be member functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Sorry, but again, spilling over functionality that is only used to structure the code to package level does not makes things better but worse.
} | ||
|
||
func (act ActionStateToMetaState) loadWorkItemByID(id uuid.UUID) (*workitem.WorkItem, error) { | ||
wi, err := act.Db.WorkItems().LoadByID(act.Ctx, id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to capture this function in a wrapper. The return value of both are the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
? Both what?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return values of the wrapper and the wrapped function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this is not for re-using functionality. This is to make the main method smaller and easier to understand what it does! Why are you trying to force your mind model into my code and internal wiring? Yes, sure, I could force everything into one gigantic method, like it is done in many other places in the WIT. Does this increase robustness? Does it increase readability? I don't think so. I could glue the onStateChange()
and onColumnChange()
back together and put it into onChange()
. Sure, all of this is possible. But does it makes sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to make the main method smaller and easier to understand what it does!
I get your point you didn't get mine. Sure if I read loadWorkItem
that is nice but this is an untested function and as you've mentioned in many other parts of the code we call act.Db.WorkItems().LoadByID(act.Ctx, id)
. That means, people know what this code does and it is not very secret or hard to read. So IMHO the mere fact that we're using it all over the code already outweighs the apparent readability which costs coverage and adds a layer over an already established function.
return metastateToStateMap, nil | ||
} | ||
|
||
func (act ActionStateToMetaState) addOrUpdateChange(changes change.Set, attributeName string, oldValue interface{}, newValue interface{}) change.Set { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a member function when act
is not used anywhere inside?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there is no need to spill over purely internal functionality to package level. Encapsulation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't say you should put it on the package level but how about making it part of the change.Set
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, one could argue about the reason you gave for not putting it as a function on package level. Here's you may not be polluting the package level but you're requiring to create an object, namely ActionStateToMetaState
, that is not needed for the execution of the function. I consider this worse because the prerequisites to run the function are just wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you missed some of the basic ideas here. There is good reason for having Actions encapsulated as structs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you missed some of the basic ideas here. There is good reason for having Actions encapsulated as structs.
You misunderstood me. I never said to not store actions as structs. All I asked for was to outsource helper functions as testable-building blocks. We've discussed that in today's Bluejeans meeting. As much as I don't like it but I can live with a test that does ActionStateToMetaState{}.addOrUpdateChange()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Fixed in 7dafce4.
return newChanges | ||
} | ||
|
||
func (act ActionStateToMetaState) fuseChanges(c1 change.Set, c2 change.Set) *change.Set { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: Why is this a member function when act
is not used anywhere inside?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same reason as above.
return &c1 | ||
} | ||
|
||
// OnChange executes the action rule. It implements rules.Action. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"executes the action rule" doesn't sound right and it is not helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same review comment was made in #2249 (comment) , which should've been fixed.
return newChanges | ||
} | ||
|
||
func (act ActionStateToMetaState) fuseChanges(c1 change.Set, c2 change.Set) *change.Set { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function could need an explanation. And it I think it is best implemented as a member of change.Set
, no? Then you can write:
var a, b change.Set
c := a.fuse(b)
Also, why does it return a pointer to a change.Set
? Below you explicitly dereference: return newContext, *actionChanges, nil
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree a.fuse(b)
makes more sense to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because this implements a very specific functionality that is not generalizable. In this case, existing changes sould be updated rather than appended in order. This is very specific to this particular use case. I don't think this makes sense as part of change.Set
. Also, I externalize to local functions for structuring the code and making is more understandable, not necessarily because there is a re-use case or it is generalizable functionality. It just does not make sense to write methods that have more than a few hundred lines. If it has, I segment them down into chunks that have a defined signature interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And it returns *actionChanges
because of exactly this reason. This is a structural method. The caller needs *actionChanges
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is somewhat confusing to me. The consumer of the fuseChanges
function is func (act ActionStateToMetaState) OnChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error)
. Inside of that function you assign the actionChanges
like so: actionChanges = act.fuseChanges(*actionChanges, executionChanges)
and then you also return the changes like so: return newContext, *actionChanges, nil
.
In the return you dereference the actionChanges
, so for that part you don't need fuseChanges
to return a pointer.
Since you also set a pointer that was a parameter before and also return the same value in OnChange
, I doubt that this is thought through. One should be enough, either set (using the parameter) or return a new value but not do both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the first point. I don't understand the second point. Anyways, I changed the return type fo fuseChanges
. 5001aa2.
} | ||
} | ||
// and return to sender. | ||
return resultingWorkItem, changes, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would be returning an empty workitem in resultingWorkitem
if there were no changes. Is this expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good find. Actually no, because the issue is somewhere else, but connected to this. In the case of the state change, isDirty
is always true at l465. So this works, but for the wrong reasons. I'll update the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
973fe2d. Checked again, indeed the isDirty
was redundant. I think this happened because I copied and refactored the onColumnChange()
method there. Again, good find.
} | ||
|
||
// onStateChange is executed when the state changes. It eventually updates the metastate and the boardcolumns. | ||
func (act ActionStateToMetaState) onStateChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a comment stating what this function is supposed to return?
Depending on https://github.com/fabric8-services/fabric8-wit/pull/2249/files#diff-d2486097896184ec82948b273169e638R458 , the first value returned is either the new
workitem or an empty
workitem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above. It returns the updated WI. Added a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
// onBoardColumnsChange is executed when the columns change. It eventually updates the metastate and the state. | ||
func (act ActionStateToMetaState) onBoardColumnsChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please update the comment to include what this function is supposed to return?
This function returns either a workitem object with field values or an empty workitem object based on the value of wiDirty
flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above. Always returns the updated WI. 973fe2d.
[test] |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We spoke about this on the PR call today. Please use workitem.ChangeSet
method instead of creating Change.Set{}
struct.
} | ||
} | ||
// return to sender | ||
return resultingWorkItem, changes, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelkleinhenz We're returning an empty workitem here if wiDirty
is false. This isn't expected, right?
Can you please fix it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Fixed. 6f4d7fb.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the redundant var in 3cb93a2.
} | ||
|
||
// OnChange executes the action rule. It implements rules.Action. Returns the Work Item with eventual changes applied. | ||
func (act ActionStateToMetaState) OnChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not like the fact that actionChanges
is a pointer
. It is passed to onStateChange
, onBoardColumnChange
by reference. If one of onStateChange
/onBoardColumnChange
fails, there is a possibility that the original object would be modified even when there was an error.
@michaelkleinhenz Can we get rid of pointers
from OnChange
, onBoardColumnChange
and onStateChange
methods? It is possible that original object is not modified when the error occurs in any of the methods but it would be better if we didn't use pointers
where they are not necessary. This would help in debugging the failures as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that is actually a good reason. I don't think that can happen with the state rule, but in case of an error, the actionChanges
could be left in an undefined state. I updated the signature. f83ff16. Note that this also required some changes to the other existing rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelkleinhenz You're awesome. Thank you for changing it 🎉
return workitem, nil | ||
} | ||
|
||
func (act ActionStateToMetaState) getValueListFromEnumField(wit *workitem.WorkItemType, fieldName string) ([]interface{}, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is wit
a pointer? Let's not use pointers at unnecessary places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. 5cd9aef.
} | ||
|
||
// onBoardColumnsChange is executed when the columns change. It eventually updates the metastate and the state. Returns the Work Item with eventual changes applied. | ||
func (act ActionStateToMetaState) onBoardColumnsChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please not use a actionChanges
as a pointer
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. See #2249 (comment)
} | ||
|
||
// onStateChange is executed when the state changes. It eventually updates the metastate and the boardcolumns. Returns the Work Item with eventual changes applied. | ||
func (act ActionStateToMetaState) onStateChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please not use a actionChanges
as a pointer
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. See #2249 (comment).
} | ||
boards, err := act.Db.Boards().List(act.Ctx, space.SpaceTemplateID) | ||
if err != nil { | ||
return nil, errs.Wrap(err, "error loading work item type: "+err.Error()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelkleinhenz You can get rid of err.Error()
. We're already returning it via errs.Wrap(err, ...)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if act.UserID == nil { | ||
return nil, nil, errs.New("userID is nil") | ||
} | ||
if actionChanges == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kwk Is this the correct way to check for empty actionChanges
? Earlier it was pointer and this check made sense but now it represents []changes
and each change is
type Set []Change
type Change struct {
AttributeName string
NewValue interface{}
OldValue interface{}
}
Should this line be replaced with len(actionChanges) > 0
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole check does not make sense for the non-pointer scenario. This was in there to check that there is actually a usable actionChanges
given. As we now always have an instance, this check is redundant. The actionChanges
is allowed to be empty here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for addressing all my concerns. Please wait for @kwk to also approve the PR.
[test] |
This contains the state to metastate mapping action rule and the necessary changes to the existing templates.
A thorough and complete description can be found here: https://docs.google.com/presentation/d/18xKQmkf3UKlGoP7qLqUnrIYiflza674n_paUZXKuepA/edit
Please note that this PR contains 2 new source files (the actual rule and the test) and minor changes to 3 existing files. The rest of the files in the PR are changes to golden files.